#pragma once
#include <time.h>
#include <iostream>
#include <string>

using namespace std;

/*
 * Value of time-stamp counter register, works for AMD64.
 */

inline ui64 GetTimeStampCounter() {
    ui64 out=0;
    asm (
        "rdtsc; \
        sal $32, %%rdx; \
        add %%rax, %%rdx; \
        mov %%rdx, %0;"
        :"=r"(out)
        :
        :"%rdx","%rax"
    );
    return out;
}
/*
 *
 */
class TStopwatch {
    enum {
        AVERAGE_TEST_CYCLES = 512
    };
    time_t start_secs;
    clock_t start_clocks;
    ui64 start_tsc;
    ui64 counts;
    ui64 cummulative_tsc;
    // average time for an empty start/stop cycle
    ui64 dummy_tsc;
public:

    void ResetStartValues() {
        start_secs = time(NULL);
        start_clocks = clock();
        start_tsc = GetTimeStampCounter();
    }    
    void SetDummyTsc() {
        dummy_tsc = 0;
        for (ui32 i = 0; i<AVERAGE_TEST_CYCLES; i++) {
            ResetTsc();
            Count();
        }
        dummy_tsc = (ui64)GetAverageTsc();
    }

    TStopwatch () : counts(0), cummulative_tsc(0) {
        ResetStartValues();
        SetDummyTsc();
    }
    TStopwatch (string msg) : counts(0), cummulative_tsc(0) {
        ResetStartValues();
        SetDummyTsc();
        cout << msg << endl;
    }

    int GetTimeShift() {
        return (int)difftime(time(NULL), start_secs);
    }
    int GetClockShift() {
        return (int)(clock() - start_clocks)/10000;
    }
    ui64 GetTscShift() {
        return GetTimeStampCounter() - start_tsc - dummy_tsc;
    }

    void ResetTsc() {
        start_tsc = GetTimeStampCounter();
    }
    void Count() {
        counts++;
        const ui64 tsc_shift = GetTscShift();
        cummulative_tsc += tsc_shift;
    }
    void ResetAverageCounter() {
        counts = 0;
        cummulative_tsc = 0;
    }
    /* Used with ResetTsc() and Count().*/
    ui64 GetAverageTsc() {
        if (counts == ui64(0)) {
            ResetAverageCounter();
            return ui64(-1);
        } else {
            ui64 res = cummulative_tsc/counts;
            ResetAverageCounter();
            return res;
        }
    }

    void Print() {
        const ui64 tsc_value = GetTimeStampCounter();
        const clock_t clocks_value = clock();
        const time_t time_value = time(NULL);
        cout <<
            "Elapsed time: "
            << ", " << difftime(time_value, start_secs) << " sec"
            << ", " << (int)(clocks_value - start_clocks)/10000 <<" clocks"
            << ", " << tsc_value-start_tsc << " tsc counts"
            << "." << endl;
    }
};
